# GameCore handle processes in core game

from abc import ABCMeta

class Card():
	
	def __init__(self, idCard):
		self.idCard = idCard	#idCard [0..51]
		self.special = False	#speacial card
		
	def __repr__(self):
		return repr((self.idCard, self.special))
		
	def __getitem__(self, i):
		if i == 0: return self.idCard
		else: return self.special
	
	def SetSpecial(self):
		#method for setting this card speacial
		self.special = True
	
	def IsSpecial(self):
		#method for checking this card speacial or not
		return self.special
	
	def GetID(self):
		#method for getting this idCard
		return self.idCard

class ArrayCards(list):
	
	def __init__(self, start = [[]]):
		list.__init__(start)
	
	def MergeArray(self, other):
		#method for merging another ArrayCards with this array
		if isinstance(other, ArrayCards):
			for i in other:
				for j in i:
					self.AddCard(j)
		else:
			for i in other:
				self.AddCard(i)
	
	def AddCard(self, card):
		#method for adding card into ArrayCards and sort by order
		if len(self) == 0:
			self.append([card])
		else:
			add = False
			for i in self:
				if i[0].GetID()%13 == card.GetID()%13:
					i.append(card)
					from operator import itemgetter
					i.sort(key = itemgetter(1, 0))
					del itemgetter
					add = True
					break
				elif i[0].GetID()%13 > card.GetID()%13:
					self.insert(self.index(i), [card])
					add = True
					break
			if not add:
				self.append([card])
	
	def GetCard(self,  idCard):
		#method for getting Card from ArrayCards with id
		for i in self:
			for j in i:
				if j.GetID() == idCard:
					return j
	
	def TakeCard(self, card):
		#method for taking out Card from ArrayCards
		for i in self:
			if card in i:
				i.remove(card)
				if len(i) == 0:
					self.remove(i)
				return
	
	def Find(self, card):
		for i in self:
			if card in i:
				return True
		return False
	
	def GetNumCards(self):
		#method for counting card in ArrayCards
		tmp = 0
		for i in self:
			tmp += len(i)
		return tmp
		
	def GetNumNormCards(self):
		#this method for counting normal card in ArrayCards
		tmp = 0
		for i in self:
			for j in i:
				if j.IsSpecial(): continue
				else: tmp += 1
		return tmp
		
	def GetAbsPos(self, idCard):
		for i in self:
			for j in i:
				if j.GetID() == idCard:
					return [self.index(i), i.index(j)]
		
	def IsEmpty(self):
		#method for checking ArrayCards empty or not
		return (len(self) == 0)
		
class ArrayCardsDeck(list):
	
	def __init__(self, start = []):
		list.__init__(start)
		
	def RandomArray(self):
		#method to random cards in array
		import random
		random.shuffle(self)
		del random
		
	def MarkSpecial(self, card):
		#method to mark all cards in a type similar with idCard is Special
		type = card.GetID()/13
		for i in self:
			if i.GetID()/13 == type:
				i.SetSpecial()
	
	def MergeArray(self, other):
		#method for merging another ArrayCards with this array
		self += other
	
	def AddCard(self, card):
		#method for adding card into ArrayCards
		self.append(card)
	
	def TakeCard(self):
		#method for taking out Card from ArrayCards
		temp = self[len(self)-1]
		self.remove(temp)
		return temp
	
	def GetNumCards(self):
		#method for counting card in ArrayCards
		return len(self)
		
	def IsEmpty(self):
		#method for checking ArrayCards empty or not
		return (len(self) == 0)
		
class Player():
	#Player class not complete
	__metaclass__ = ABCMeta
	
	# Player stage
	NO_STAGE			=	300
	CAN_DEFEND		=	301
	CANT_DEFEND		=	302
	CAN_ATTACK		=	303
	CANT_ATTACK		=	304
	WIN				=	305
	MUST_TAKE_CARDS	=	306
	
	# Player type in session
	ACTIVE			=	310
	PASSIVE			=	311
	SUPPORT			=	312
	DEACTIVE			=	313
	
	# Player type
	PERSON			=	320
	BOT				=	321
	
	# Player stage in session
	PLAY				=	330
	WAIT				=	331
	
	def __init__(self, name, type, stage = NO_STAGE, typeInSession = DEACTIVE, stageInSession = WAIT):
		self.name = name				#name of player
		self.type = type					#type of player
		self.stage = stage				#stage of player
		self.typeInSession = typeInSession		#type of player in a turn in game
		self.stageInSession = stageInSession	#stage of player in session
		self.cards = ArrayCards()			#save cards in hand of player
		self.cardsCanPlay = []				#save cards can play or reply
		
	def __repr__(self):
		return repr((self.name, self.type, self.stage, self.typeInSession, self.cards, self.cardsCanPlay))
		
	def GetType(self):
		#method for getting type of player
		return self.type
		
	def SetTypeInSession(self, type):
		#method for setting type in session of player
		self.stage = Player.NO_STAGE
		self.typeInSession = type
		
	def GetTypeInSession(self):
		#method for getting type in session of player
		return self.typeInSession
		
	def SetStageInSession(self, stage):
		self.stageInSession = stage
	
	def GetStageInSession(self):
		return self.stageInSession
		
	def GetCardsCanPlay(self):
		#method for getting cards can play
		return self.cardsCanPlay
		
	def GetNumCards(self):
		#method for getting num of card in hand
		return self.cards.GetNumCards()
		
	def ReceiveCard(self, card):
		#method for recieving card
		if isinstance(card, Card):
			self.cards.AddCard(card)
		else:
			for i in card:
				self.cards.AddCard(i)
		self.stage = Player.NO_STAGE
		
	def NumCardsToTake(self):
		#method for checking and return how many card must recieve form cards in deck
		numTake = 8 - self.cards.GetNumCards()
		if numTake > 0:
			return numTake
		else: return 0
		
	def PlayCard(self, Card):
		#method for playing card to play or reply
		self.cards.TakeCard(Card)
		
	def CheckAvailable(self, card, numCardOfDefender, deckEmpty):
		#method for checking available cards can play with cardInTurn is array of cards is playing
		#	card can be Card if defend
		#	card can be ArrayCard if attack, require numCardOfDefender
		
		del self.cardsCanPlay
		self.cardsCanPlay = []
		onlySpecial = False
		
		#have no card
		if self.cards.IsEmpty():
			#if deck have card, must take cards from deck
			if not deckEmpty:
				self.stage = Player.MUST_TAKE_CARDS
			#deck have no card, win
			else: self.stage = Player.WIN
			return
			
		#only one normal card, only play special card
		if self.cards.GetNumCards()>1 and self.cards.GetNumNormCards() == 1:
			onlySpecial = True
		
		#check available cards for attacker
		if isinstance(card, list):
			#Defender has only 1 card remain. Available cards must not be special card or Ace card.
			if numCardOfDefender == 1:
				#only one normal card, cant attack
				if onlySpecial:
					self.stage = Player.CANT_ATTACK
					return
				#if have card in session, check norm card can play
				if len(card) > 0:
					for j in card:
						for i in self.cards:
							if i[0].GetID()%13 == j.GetID()%13 and i[0].GetID()%13 <> 12:
								for k in i:
									if not k.IsSpecial():
										self.cardsCanPlay.append(k)
				#no card play yet, can play all norm card
				else:
					for i in self.cards:
						for k in i:
							if not k.IsSpecial():
								cardsCanPlay.append(k)
			#Defender has more than 1 card
			elif numCardOfDefender > 1:
				#if have card in session, check card can play
				if len(card) > 0:
					for j in card:
						for i in self.cards:
							if i[0].GetID()%13 == j.GetID()%13:
								for k in i:
									#if only play special card, only special card will add
									if onlySpecial:
										if k.IsSpecial():
											self.cardsCanPlay.append(k)
									#can play all card
									else: self.cardsCanPlay.append(k)
				#if self first, check card can play
				else:
					for i in self.cards:
						for k in i:
							#if only play special card, only special card will add
							if onlySpecial:
								if k.IsSpecial():
									self.cardsCanPlay.append(k)
							#can play all card
							else: self.cardsCanPlay.append(k)
			#have problem here
			else: raise
			#change stage
			if len(self.cardsCanPlay) >0:
				self.stage = Player.CAN_ATTACK
			else: self.stage = Player.CANT_ATTACK
		#check available cards for defender
		else:
			#find all cards with the same type and larger
			sizePlay = card.GetID()%13
			kindPlay = card.GetID()/13
			for i in self.cards:
				for k in i:
					if not card.IsSpecial():
						if k.IsSpecial():
							self.cardsCanPlay.append(k)
							continue
					if k.GetID()/13 == kindPlay:
						if k.GetID()%13 > sizePlay:
							self.cardsCanPlay.append(k)
			#change stage
			if len(self.cardsCanPlay) >0:
				self.stage = Player.CAN_DEFEND
			else: self.stage = Player.CANT_DEFEND
	
	def GetStage(self):
		#method for getting stage of player
		return self.stage
			
class Bot(Player):
	#Bot level
	EASY		=	400
	MEDIUM	=	401
	HARD		=	402
	
	def __init__(self, name, type = 2, level = HARD):
		Player.__init__(self, name, Player.BOT)
		self.level = level					#level of AI
		
		#information for AI method calculate
		self.kindCard = None				#save kind of card just play
		self.kindRepBySpecial = []			#save kind of card reply by special
		self.baseAI = []					#save cards in hand of other player
		if type == 2:
			self.baseAI.append(ArrayCards())
		else:
			for i in range(3):
				self.baseAI.append(ArrayCards())
		
	def CheckNum(self, size, arrayCard):
		#method return num of card have the same size
		if len(arrayCard) == 0: return 0
		if isinstance(arrayCard, ArrayCards):
			if size == 0:
				if arrayCard[0][0].GetID()%13 == size:
					return len(arrayCard[0])
			else:
				n = size
				if size>len(arrayCard):
					n = len(arrayCard)
				for j in range(n):
					if arrayCard[j][0].GetID()%13 == size:
						return len(arrayCard[j])
		else:
			num = 0
			for j in arrayCard:
				if j.GetID()%13 == size:
					num += 1
			return num
		return 0
		
	def UpdateAI(self, card, sidePlay = False, stage = None):
		#method for updating information base for AI method
		if isinstance(card, Card):
			#save kind of card adversarial reply by special only for attacker
			if self.typeInSession in [Player.ACTIVE, Player.SUPPORT]:
				if sidePlay:
					self.kindCard = card.GetID()/13
					if self.stageInSession == Player.PLAY:
						return
				else:
					kindRep = card.GetID()/13
					if self.typeInSession == Player.ACTIVE:
						if kindRep in self.kindRepBySpecial:
							self.kindRepBySpecial[0].remove(kindRep)
						if kindRep<>self.kindCard:
							if len(self.kindRepBySpecial) == 0:
								self.kindRepBySpecial.append([self.kindCard])
							else:
								self.kindRepBySpecial[0].append(self.kindCard)
					elif self.typeInSession == Player.SUPPORT:
						if kindRep in self.kindRepBySpecial:
							self.kindRepBySpecial[1].remove(kindRep)
						if kindRep<>self.kindCard:
							l = len(self.kindRepBySpecial)
							if  l == 0:
								self.kindRepBySpecial.append([])
								self.kindRepBySpecial.append([self.kindCard])
							elif l == 1:
								self.kindRepBySpecial.append([self.kindCard])
							else:
								self.kindRepBySpecial[1].append(self.kindCard)
			
			#remove card out of information base for AI if have
			for i in self.baseAI:
				if i.Find(card):
					i.TakeCard(card)
		else:
			#if have player cant reply, update information base for AI
			if stage == Player.CANT_DEFEND:
				if self.typeInSession == Player.ACTIVE:
					for i in card:
						self.baseAI[0].AddCard(i)
				elif self.typeInSession == Player.DEACTIVE:
					for i in card:
						self.baseAI[1].AddCard(i)
				elif self.typeInSession == Player.SUPPORT:
					for i in card:
						self.baseAI[2].AddCard(i)
	
	def ChooseCard(self, cardsDone):
		#method for AI choose card to play
		markOfCards = []			#save all mark of card can play
		
		if len(self.cardsCanPlay) == 0:
			return
		#calculate mark for each card can play
		for i in self.cardsCanPlay:
			mark = 0			#save mark of card
			
			"""
			For each card
				Calculating common strategy mark
							stage		mark			max
					inHand	high		high			4
					inSupport	high		quite high		3(4)
					special	true		very very low	1
					inRight	high		very low		3(4)
					inLeft		high		very low		3(4)
					size		high		very low		12
					
				Calculating strategic attack mark
							stage		mark		max
					cantReply	true		very high	1
					inDone	high		quite low	3(4)
					
				Calculating strategic defend mark
							stage		mark		max
					inDone	high		very high	3(4)
			"""
			
			#Main strategy - share
			#Bot easy
			#	check only card size and special
			special = 0			#equal 1 if card is special			
			if i.IsSpecial(): special = 1
			size = i.GetID()%13	#size of card
			#Bot Medium
			#	count card have group in hand an supporter
			inHand = 0
			inSupport = 0
			if self.level >= Bot.MEDIUM:
				inHand = self.CheckNum(size, self.cards)
				if len(self.baseAI) >1:
					inSupport = self.CheckNum(size, self.baseAI[1])
			#Bot Hard
			#	count card have group in hand of adversarial and played
			inLeft = 0
			inRight = 0
			inDone = 0
			if self.level == Bot.HARD:
				inDone = self.CheckNum(size, cardsDone)
				inRight = self.CheckNum(size, self.baseAI[0])
				if len(self.baseAI) >1:
					inLeft = self.CheckNum(size, self.baseAI[2])
			#Calculate common strategy mark
			mark += (13*(3*(1-special)+(12-size)+3-inLeft+3-inRight)+6*(inHand)+1*(inSupport))
			
			#Strategic attack - bot choose card for play
			if self.typeInSession == Player.ACTIVE or self.typeInSession == Player.SUPPORT:
				cantReply = 0
				#For level hard only
				if self.level == Bot.HARD:
					#raise mark for kind of card adversarial reply by special
					for j in self.kindRepBySpecial:
						for k in j:
							if k == i.GetID()/13:
								cantReply = 1
								break
				#Calculating mark attack
				mark += (13*(cantReply)+7*(3-inDone))	
			#Strategic defend - bot choose card for reply
			else:
				#If have, add here
				
				#Calculating mark defend
				mark += (13*(inDone))
			
			#save mark
			markOfCards.append(mark)
		
		#return Card with max mark
		max = 0
		for i in markOfCards:
			if max<i:
				max = i
		return self.cardsCanPlay[markOfCards.index(max)]

class Person(Player):
	
	def __init__(self, name):
		Player.__init__(self, name, Player.PERSON)

class Session():
	
	def __init__(self, players):
		self.cardsInSession = []		#save cards in a session
		self.Active = players[0]		#save active player in session
		self.Passive = players[1]		#save passive player in session
		self.Support = players[2]		#save support player in session
		self.Deactive = players[3]		#save deactive player in session
		
		self.Active.SetTypeInSession(Player.ACTIVE)
		self.Active.SetStageInSession(Player.PLAY)
		self.Passive.SetTypeInSession(Player.ACTIVE)
		if self.Support <> None:
			self.Support.SetTypeInSession(Player.SUPPORT)
			self.Support.SetStageInSession(Player.WAIT)
		if self.Deactive <> None:
			self.Deactive.SetTypeInSession(Player.DEACTIVE)
			
	def ChangeActive(self):
		if self.Support == None:
			return None
		stageInSession = self.Active.stageInSession
		self.Active.stageInSession = self.Support.stageInSession
		self.Support.stageInSession = stageInSession
		if stageInSession == Player.PLAY:
			return self.Support
		else:
			return self.Active

class Table():
	
	# Table type
	TWO_PLAYER	=	800
	FOUR_PLAYER	=	801
	
	def __init__(self, type = TWO_PLAYER):
		self.type = type					#type of table
		self.cardInDeck = ArrayCardsDeck()	#save cards in deck
		self.cardsDone = ArrayCards()		#save cards done
		self.players = []					#save players in table
		
		#initing players
		self.players.append(Person(u"Player"))
		if type == Table.TWO_PLAYER:
			self.players.append(Bot(u"Bot"))
		elif type == Table.FOUR_PLAYER:
			for i in range(3):
				self.players.append(Bot(u"Bot" + str(i+1), 4))
		else: raise TypeError
		
		#initing cards in deck
		for i in range(52):
			self.cardInDeck.AddCard(Card(i))
		self.cardInDeck.RandomArray()
		self.cardInDeck.MarkSpecial(self.cardInDeck[0])
		
	def __repr__(self):
		return repr((self.type, self.cardInDeck))
		
	def GetPlayers(self):
		#method return the array of players in game
		return self.players
		
	def GetOrderSession(self, idFirstPlay):
		#method return list by order attacker, defender, supporter, deactive player
		if self.type == Table.TWO_PLAYER:
			return [self.players[idFirstPlay], self.players[(idFirstPlay+1)%2], None, None]
		else:
			return[self.players[idFirstPlay], self.players[(idFirstPlay+1)%4], self.players[(idFirstPlay+2)%4], self.players[(idFirstPlay+3)%4]]
		
	def GetNumCardsInDeck(self):
		return len(self.cardInDeck)
		
	def GetNumPlayers(self):
		if self.type == Table.TWO_PLAYER:
			return 2
		else: return 4
		
	def GetSpecial(self):
		#method return the card which is chosen is special in this game
		return self.cardInDeck[0]
		
	def GetFirstPlayer(self):
		#method return id of first player will play
		return (self.cardInDeck[0].GetID()%13)%self.GetNumPlayers()
	
	def ServeCards(self):
		#method for serving card for players
		for j in range(8):
			for i in self.players:
				i.ReceiveCard(self.cardInDeck.TakeCard())
		
	def TakeCardsInDeck(self, player):
		#method for taking cards for player if has not enough cards from cardInDeck
		if player not in self.players: raise
		if self.cardInDeck.IsEmpty(): return False
		num = player.NumCardsToTake()
		for j in range(num):
			if self.cardInDeck.IsEmpty(): return False
			player.ReceiveCard(self.cardInDeck.TakeCard())
	
	def IsDeckEmpty(self):
		return self.cardInDeck.IsEmpty()
#Test 2p mode
"""
a = Table()
#print a
#print a.GetSpecial()
a.ServeCards()
for i in a.GetPlayers():
	print i
	if i.GetType() == Player.BOT:
		b = i
#Attack
print u"Attack"
c = []
b.SetTypeInSession(Player.ACTIVE)
b.CheckAvailable(c, 8)
print b.GetStage(), b.cardsCanPlay
if b.GetStage() == Player.CAN_ATTACK:
	print b.ChooseCard(a.cardsDone)
#Defend
print u"Defend"
d = Card(1)
b.SetTypeInSession(Player.PASSIVE)
b.CheckAvailable(d)
print b.GetStage(), b.cardsCanPlay
if b.GetStage() == Player.CAN_DEFEND:
	print b.ChooseCard(a.cardsDone)
else:
	print u"Cant defend"
	
import sys
sys.exit()
"""
